/**
 * Copyright (c) 2004,2005 UCLA Compilers Group. 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 *  Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 * 
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 * 
 *  Neither UCLA nor the names of its contributors may be used to endorse 
 *  or promote products derived from this software without specific prior 
 *  written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **/

/*
 * All files in the distribution of JTB, The Java Tree Builder are
 * Copyright 1997, 1998, 1999 by the Purdue Research Foundation of Purdue
 * University.  All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this entire copyright notice is duplicated in all
 * such copies, and that any documentation, announcements, and
 * other materials related to such distribution and use acknowledge
 * that the software was developed at Purdue University, West Lafayette,
 * Indiana by Kevin Tao and Jens Palsberg.  No charge may be made
 * for copies, derivations, or distributions of this material
 * without the express written consent of the copyright holder.
 * Neither the name of the University nor the name of the author
 * may be used to endorse or promote products derived from this
 * material without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE.
 */

package visitor;

import syntaxtree.*;
import misc.*;
import java.io.*;
import java.util.*;

//
// This class functions as follows:
//   - in visitBNFProduction, redirects out to a StringWriter buffer.
//   - descends tree where the RHS is printed into buffer, and varList is built
//   - traverse varList, print variable declarations
//   - print the Block, then the buffer
//
// Maybe for checking, take a Hashtable of ClassInfos generated by
// ClassGenerator and make sure each class was generated before putting it in
// the list.
//
// NOTE: to have a node print itself and its subtree without annotating it
// (e.g. LocalLookahead), use n.accept(plainPrinter).
//

/**
 * Class Annotator generates the annotated .jj file containing tree-building
 * code.
 *
 * Annotator and ClassGenerator depend on each other to create classes
 * compatible with each other so any change in how classes are generated
 * might break the other.
 */
public class Annotator extends Printer {
   private Printer plainPrinter; // if lower nodes don't need annotation

   private int varNum;
   private Vector varList;       // List of all variables to be declared

   private Vector outerVars;     // List of all outer vars for constructor
   private VarInfo prevVar;      // The last variable generated so far

   private boolean annotateNode; // set to false for Blocks and LocalLookaheads
   
   private String curProduction; // for use in error reporting

   //
   // Variable to use in fixing nested list bug.
   //
   private Vector specialList = null;   // list of specials to initialize;

   public Annotator() throws FileExistsException {
      this(Globals.outFilename);
   }

   public Annotator(String filename) throws FileExistsException {
      super();
      try {
         File file = new File(filename);

         if ( Globals.noOverwrite && file.exists() )
            throw new FileExistsException(filename);
         else {
            out = new PrintWriter(new FileOutputStream(file));
            plainPrinter = new Printer(out, spc);
         }
      }
      catch (IOException e) {
         Errors.hardErr(e);
      }
   }
   public Annotator(Writer w) {
      super(w);
      out = new PrintWriter(w);
      plainPrinter = new Printer(out, spc);
   }
   public Annotator(OutputStream o) {
      super(o);
      out = new PrintWriter(o);
      plainPrinter = new Printer(out, spc);
   }

   private String curVarName()   { return "n" + String.valueOf(varNum++); }
   private void resetVarNum()    { varNum = 0; }

   //
   // Simply moved the common end-code from all the annotation methods here.
   //
   private void finalActions(VarInfo info) {
      if ( nestLevel == 0 ) outerVars.addElement(info);
      else {
         prevVar = info;
         annotateNode = true;
      }
   }

   //
   // User generated visitor methods below
   //
   // Note--we only want BNF productions annotated, so plainPrinter is used
   // as a safety precaution so lower nodes we don't want annotated, aren't.
   //
   // f0 -> JavaCCOptions()
   // f1 -> < PARSER_BEGIN_TK > 
   // f2 -> < LPAREN > 
   // f3 -> < IDENTIFIER > 
   // f4 -> < RPAREN > 
   // f5 -> CompilationUnit()
   // f6 -> < PARSER_END_TK > 
   // f7 -> < LPAREN > 
   // f8 -> < IDENTIFIER > 
   // f9 -> < RPAREN > 
   // f10 -> ( Production() )*
   // f11 -> < EOF > 
   //
   public void visit(JavaCCInput n) {
      out.println(spc.spc + Globals.fileHeader(spc));
      out.println();
      out.print(spc.spc);
      n.f0.accept(plainPrinter);
      out.println("\n");
      out.println(spc.spc + n.f1 + n.f2 + n.f3 + n.f4);
      out.println(spc.spc + ImportInserter.unitAddImport(n.f5));
      out.println(spc.spc + n.f6 + n.f7 + n.f8 + n.f9 + "\n");
      out.print(spc.spc);
      visit(n.f10, "\n\n" + spc.spc);
      out.println();
      flushWriter();
   }

   public void visit(JavaCodeProduction n) {
      n.accept(plainPrinter);
   }

    /**
     * new Grammar production:
     * f0 -> ResultType()
     * f1 -> <IDENTIFIER>
     * f2 -> FormalParameters()
     * f3 -> <COLON>
     * f4 -> "{"
     * f5 -> ( BlockStatement() )*
     * f6 -> "}"
     * f7 -> <LBRACE>
     * f8 -> ExpansionChoices()
     * f9 -> <RBRACE>
     */
    
    /**
     * old Grammar production: 
     * f0 -> ResultType()
     * f1 -> <IDENTIFIER>
     * f2 -> FormalParameters()
     * f3 -> <COLON>
     * f4 -> Block()
     * f5 -> <LBRACE>
     * f6 -> ExpansionChoices()
     * f7 -> <RBRACE>
     */

   public void visit(BNFProduction n) {
      String rhs;

      curProduction = n.f1.toString();
      out.println(n.f1 + " " + n.f1 + javaString(n.f2) + " " + n.f3);

      //
      // Generate the RHS into buffer
      //
      varList = new Vector();
      outerVars = new Vector();
      prevVar = null;
      resetVarNum();
      nestLevel = 0;
      annotateNode = true;

      rhs = generateRHS(n);

      //
      // Print the variable declarations
      //
      out.println(spc.spc + "{");
      spc.updateSpc(+1);

      for ( Enumeration e = varList.elements(); e.hasMoreElements(); )
        out.println(spc.spc + ((VarInfo)e.nextElement()).getVarDeclString());
        
      out.println();
      out.println(spc.spc + javaString(n.f5));
      spc.updateSpc(-1);
      out.println(spc.spc + "}");
      out.print(spc.spc + rhs);
   }

   //
   // Returns a string with the RHS of the current BNF production.  When
   // this function returns, varList and outerVars will have been built and
   // can be used by the calling method.
   //
   private String generateRHS(BNFProduction n) {
      PrintWriter temp = out;
      StringWriter buf = new StringWriter();
      out = new PrintWriter(buf);
      plainPrinter.setOut(out);

      out.println(n.f7);
      spc.updateSpc(+1);
      out.print(spc.spc);
      n.f8.accept(this);
      out.println();
      out.print(spc.spc + "{ return new " + n.f1 + "(");

      Enumeration e = outerVars.elements();
      if ( e.hasMoreElements() ) {
         out.print(((VarInfo)e.nextElement()).getName());
         for ( ; e.hasMoreElements(); )
            out.print("," + ((VarInfo)e.nextElement()).getName());
      }

      out.println("); }");
      spc.updateSpc(-1);
      out.print(spc.spc + n.f9);
      out.flush();
      out.close();

      out = temp;
      plainPrinter.setOut(out);

      return buf.toString();
   }

   public void visit(RegularExprProduction n) {
      n.accept(plainPrinter);
   }

   //
   // All expansion choices should have something besides blocks or
   // lookaheads in them so if there isn't, an error will be generated.
   //
   // f0 -> Expansion()
   // f1 -> ( < BIT_OR >  Expansion() )*
   //
   public void visit(ExpansionChoices n) {
      if ( !n.f1.present() )
         n.f0.accept(this);
      else {
         String name = curVarName();
         VarInfo info = new VarInfo(Globals.choiceName, name);
         varList.addElement(info);

         generateChoices(n, name);
         out.println();
         out.print(spc.spc);

         finalActions(info);
      }
   }

   private void generateChoices(ExpansionChoices n, String ident) {
      int whichVal = 0;

      out.println("(");
      spc.updateSpc(+1);
      out.print(spc.spc);

      ++nestLevel;
      n.f0.accept(this);       // visit the first choice
      --nestLevel;

      if ( !annotateNode )
         Errors.softErr("Empty NodeChoice in " + curProduction + "()");
      else {
         out.println("{ " + ident + " = new NodeChoice(" + prevVar.getName() +
                     ", " + String.valueOf(whichVal) + "); }");
         ++whichVal;
      }

      //
      // Visit the remaining choices
      //
      for ( Enumeration e = n.f1.elements(); e.hasMoreElements(); ) {
         NodeSequence seq = (NodeSequence)e.nextElement();
         ++nestLevel;
         spc.updateSpc(-1);
         out.println(spc.spc + seq.elementAt(0));
         spc.updateSpc(+1);
         out.print(spc.spc);
         seq.elementAt(1).accept(this);
         --nestLevel;

         if ( !annotateNode )
            Errors.softErr("Empty NodeChoice in " + curProduction + "()");
         else {
            out.println("{ " + ident + " = new NodeChoice(" +
                        prevVar.getName() + ", " + String.valueOf(whichVal) +
                        "); }");
            ++whichVal;
         }
      }
      spc.updateSpc(-1);
      out.print(spc.spc + ")");
   }

   //
   // f0 -> ( ExpansionUnit() )*
   //
   public void visit(Expansion n) {
      if ( nestLevel == 0 ) n.f0.accept(this);
      else {
         ExpansionUnitTypeCounter v = new ExpansionUnitTypeCounter();
         n.accept(v);

         if ( v.getNumNormals() == 0 )       annotateNode = false;
         else if ( v.getNumNormals() == 1 ) {
            n.f0.accept(this);

            //
            // The line below fixes the C grammar bug where something like
            // ( A() | B() | C() { someJavaCode(); } ) would cause an
            // "Empty NodeChoice" error
            //
            annotateNode = true;
         }
         else {
            String name = curVarName();
            VarInfo info = new VarInfo(Globals.sequenceName, name);
            varList.addElement(info);

            generateSequence(n, name);

            prevVar = info;
            annotateNode = true;
         }
      }
   }

   //
   // Here, any NodeSequences must be initialized before any nodes in the
   // sequence occur, BUT after the first lookahead, if present (or else
   // JavaCC will complain).
   //
   private void generateSequence(Expansion n, String ident) {
      //
      // If the first unit is a lookahead, print it first
      //
      Enumeration e = n.f0.elements();
      ExpansionUnit unit;
      String sizeStr;

      if ( !e.hasMoreElements() ) {
         Errors.warning("Generating empty NodeSequence in " + curProduction +
                        "()");
         out.println("{ " + ident + " = new NodeSequence(); }");
         out.print(spc.spc);
      }
      else {
         unit = (ExpansionUnit)e.nextElement();
         sizeStr = Integer.toString(n.f0.size());

         if ( unit.f0.which == 0 ) {
            unit.accept(this);
            out.println("{ " + ident + " = new NodeSequence(" + sizeStr + "); }");
            out.print(spc.spc);
         }
         else {
            out.println("{ " + ident + " = new NodeSequence(" + sizeStr + "); }");
            out.print(spc.spc);
            ++nestLevel;
            unit.accept(this);
            --nestLevel;

            if ( annotateNode ) {
               out.println(addNodeString(ident, prevVar.getName()));
               out.print(spc.spc);
            }
         }

         //
         // Annotate any nodes in the list that need to be
         //
         for ( ; e.hasMoreElements(); ) {
            ++nestLevel;
            ((Node)e.nextElement()).accept(this);
            --nestLevel;

            if ( annotateNode ) {
               out.println(addNodeString(ident, prevVar.getName()));
               out.print(spc.spc);
            }
         }
      }
   }

   private String addNodeString(String parentName, String varName) {
      return "{ " + parentName + ".addNode(" + varName + "); }";
   }

   //
   // f0 -> LocalLookahead()
   //       | Block() 
   //       | <LPAREN> ExpansionChoices() <RPAREN> [ <PLUS> | <STAR> | <HOOK> ]
   //       | < LBRACKET >  ExpansionChoices() < RBRACKET >  
   //       | [ PrimaryExpression() < ASSIGN >  ] ExpansionUnitTerm() 
   //
   public void visit(ExpansionUnit n) {
      NodeSequence seq;
      switch ( n.f0.which ) {
         case 0:  // LocalLookahead -- fall through
         case 1:  // Block
            n.accept(plainPrinter);
            out.println();
            out.print(spc.spc);
            annotateNode = false;
            break;
         case 2:  // Parenthesized expansion
            parenExprCode(n);
            break;
         case 3:  // Optional expansion
            //
            // Convert this [ ] expansion into a ()? expansion and pass it to
            // parenExprCode()
            //
            NodeOptional optHook = new NodeOptional();
            NodeSequence parenSeq = new NodeSequence(4);
            NodeChoice choice1;
            NodeChoice choice2;
            seq = (NodeSequence)n.f0.choice;

            parenSeq.addNode(new NodeToken("("));
            parenSeq.addNode((ExpansionChoices)seq.elementAt(1));
            parenSeq.addNode(new NodeToken(")"));
            choice1 = new NodeChoice(new NodeToken("?"), 2);
            optHook.addNode(choice1);
            parenSeq.addNode(optHook);
            choice2 = new NodeChoice(parenSeq, 2);

            parenExprCode(new ExpansionUnit(choice2));
            break;
         case 4:  // Nonterminal
            seq = (NodeSequence)n.f0.choice;
            NodeOptional opt = (NodeOptional)seq.elementAt(0);

            if ( opt.present() ) {
               NodeSequence seq1 = (NodeSequence)opt.node;
               out.print(javaString(seq1.elementAt(0)) + seq1.elementAt(1));
            }

            seq.elementAt(1).accept(this);
            break;
         default:
            Errors.hardErr("n.f0.which = " + String.valueOf(n.f0.which));
            break;
      }
   }

   //
   // seq -> <LPAREN> ExpansionChoices() <RPAREN> [ <PLUS> | <STAR> | <HOOK > ] 
   //
   private void parenExprCode(ExpansionUnit n) {
      NodeSequence seq = (NodeSequence)n.f0.choice;
      ExpansionChoices ec = (ExpansionChoices)seq.elementAt(1);
      String mod;
      String name;

      out.println(seq.elementAt(0));
      spc.updateSpc(+1);
      out.print(spc.spc);

      if ( !((NodeOptional)seq.elementAt(3)).present() ) {
         //
         // No EBNF modifier present, so either generate a NodeChoice or a
         // NodeSequence.
         //
         if ( ec.f1.present() )     // generate a NodeChoice
            ec.accept(this);
         else {                     // generate a NodeSequence
            ExpansionUnitTypeCounter v = new ExpansionUnitTypeCounter();
            ec.accept(v);

            if ( v.getNumNormals() == 0 )
               Errors.warning("Empty parentheses in " + curProduction + "()",
                  ((NodeToken)seq.elementAt(0)).beginLine);

            name = curVarName();
            VarInfo info = new VarInfo(Globals.sequenceName, name);
            varList.addElement(info);

            generateSequence(ec.f0, name);

/* ****These lines commented out and finalActions() added below as****
   ****a tentative bug fix for the extra parentheses misbehavior. ****
            prevVar = info;
            annotateNode = true;
*/
            finalActions(info);
         }

         out.println();
         spc.updateSpc(-1);
         out.print(spc.spc);
         seq.elementAt(2).accept(plainPrinter);
         seq.elementAt(3).accept(plainPrinter);
         out.println();
      }
      else {
         //
         // An EBNF modifier is present so generate the appropriate structure.
         //
         mod = ((NodeChoice)((NodeOptional)
                      seq.elementAt(3)).node).choice.toString();
         name = curVarName();
         VarInfo info;
         NodeListOptional list = ec.f0.f0;     // The list of expansion units
         ExpansionUnitTypeCounter vc = new ExpansionUnitTypeCounter();

         ec.accept(vc);

         //
         // Print the first item first if it's a lookahead and NOT within
         // a Choice.
         //
         if ( !list.present() || vc.getNumNormals() == 0 ) {
            // technically, we should only generate an error if it's not a 
            // choice, but that greatly complicates things and an empty
            // choice is probably useless.
            Errors.softErr("Empty EBNF expansion in " + curProduction + "()",
               ((NodeToken)seq.elementAt(0)).beginLine);
         }
         else {
            ExpansionUnit firstUnit = (ExpansionUnit)list.nodes.elementAt(0);

            if ( specialList == null )       // top level special
               info = infoForMod(name, mod, true);
            else {                           // nested special
               info = infoForMod(name, mod, false);
               specialList.addElement(info);
            }
            varList.addElement(info);

            if ( ec.f1.present() || firstUnit.f0.which != 0 )
               generateSpecial(n, name);
            else {   // 1st item IS lookahead and not in Choice
               Vector v = list.nodes;
               
               firstUnit.accept(plainPrinter);   // print the lookahead
               out.println();
               out.print(spc.spc);
               v.removeElementAt(0);            // don't print lookahead twice
               generateSpecial(n, name);
               v.insertElementAt(firstUnit, 0); // restore list to orig state
            }

            if ( annotateNode )
               out.print(addNodeString(name, prevVar.getName()));

            finalActions(info);
         }

         out.println();
         spc.updateSpc(-1);
         out.print(spc.spc);
         seq.elementAt(2).accept(plainPrinter);
         seq.elementAt(3).accept(plainPrinter);
         out.println();

         //
         // Added for 1.0.1 -- trim vectors to smallest size
         //
         if ( ((NodeOptional)seq.elementAt(3)).present() )
            if ( mod.equals("*") || mod.equals("+") )
               out.println(spc.spc + "{ " + name + ".nodes.trimToSize(); }");
      }

      out.print(spc.spc);
   }
   
   private void generateSpecial(ExpansionUnit n, String ident) {
      NodeSequence seq = (NodeSequence)n.f0.choice;
      ExpansionChoices ec = (ExpansionChoices)seq.elementAt(1);

      //
      // Redirect to generate a list of specials nested in this one
      //
      PrintWriter tempOut = out;
      StringWriter buf = new StringWriter();
      out = new PrintWriter(buf);
      plainPrinter.setOut(out);

      Vector tempSpecialList = specialList;
      specialList = new Vector();

      ++nestLevel;
      ec.accept(this);
      --nestLevel;

      if ( specialList.size() > 0 ) {  // we have nested specials
         for ( Enumeration e = specialList.elements(); e.hasMoreElements(); ) {
            VarInfo vi = (VarInfo)e.nextElement();
            tempOut.println("{ " + vi.getName() + " = new " + vi.getType() +
                            "(); }");
            tempOut.print(spc.spc);
         }
      }

      //
      // Restore original state
      //
      out.flush();
      out = tempOut;
      plainPrinter.setOut(out);
      specialList = tempSpecialList;
      out.print(buf.toString());       // print stored buffer
   }

   private VarInfo infoForMod(String ident, String mod, boolean initializer) {
      if ( initializer ) {
         if ( mod.equals("+") )
            return new VarInfo("NodeList", ident, "new NodeList()");
         else if ( mod.equals("*") )
            return new VarInfo("NodeListOptional", ident,
                               "new NodeListOptional()");
         else if ( mod.equals("?") )
            return new VarInfo("NodeOptional", ident, "new NodeOptional()");
         else 
            Errors.hardErr("Illegal EBNF modifier: " + mod);
      }
      else {
         if ( mod.equals("+") )
            return new VarInfo("NodeList", ident);
         else if ( mod.equals("*") )
            return new VarInfo("NodeListOptional", ident);
         else if ( mod.equals("?") )
            return new VarInfo("NodeOptional", ident);
         else
            Errors.hardErr("Illegal EBNF modifier: " + mod);
      }

      return null;      // shouldn't happen
   }

   //
   // f0 -> RegularExpression()
   //       | < IDENTIFIER >  Arguments() 
   //
   public void visit(ExpansionUnitTerm n) {
      if ( n.f0.which == 0 )
         n.f0.accept(this);
      else {
         NodeSequence seq = (NodeSequence)n.f0.choice;
         String name = curVarName();
         VarInfo info = new VarInfo(seq.elementAt(0).toString(), name);
         varList.addElement(info);

         out.print(name + "=");
         seq.elementAt(0).accept(plainPrinter);
         seq.elementAt(1).accept(plainPrinter);
         out.println();
         out.print(spc.spc);

         finalActions(info);
      }
   }

   public void visit(LocalLookahead n) {
      // Don't want to annotate these
      n.accept(plainPrinter);
   }

   //
   // Here, we need 1 var for the Token, and 1 var for the NodeToken.
   // we always annotate these
   //
   // f0 -> < STRING_LITERAL > 
   //       | <LT> [ [ <POUND> ] <IDENTIFIER> <COLON> ] ComplexRegularExpressionChoices() <GT>
   //       | < LT >  < IDENTIFIER >  < GT >  
   //       | < LT >  < EOF_TK >  < GT >  
   //
   public void visit(RegularExpression n) {
      String nodeName = curVarName();
      String tokenName = curVarName();
      VarInfo nodeInfo = new VarInfo(Globals.tokenName, nodeName);
      VarInfo tokenInfo = new VarInfo("Token", tokenName);
      varList.addElement(nodeInfo);
      varList.addElement(tokenInfo);

      out.print(tokenName + "=");
	  n.f0.accept(plainPrinter);
	  if (n.f0.which==3) {
	  //
	  // Compensate for the position of <EOF>
	  //
	      out.println(" { ");
		  spc.updateSpc(+1);
		  out.println(spc.spc + tokenName + ".beginColumn++; " + tokenName + ".endColumn++;");
		  out.println(spc.spc + nodeName + " = JTBToolkit.makeNodeToken(" + tokenName + ");");
		  spc.updateSpc(-1);
		  out.println(spc.spc + "}");
	  }
	  else {
		  out.println(" { " + nodeName + " = JTBToolkit.makeNodeToken(" + tokenName + "); }");
	  }

      out.print(spc.spc);
      finalActions(nodeInfo);
   }
}

//
// A class to determine if an import statement for the syntax tree package is
// needed in the grammar file and prints the compilation unit, inserting the
// import statement if necessary.
//
class ImportInserter extends JavaPrinter {
   private ImportInserter(PrintWriter o) {
      super();
      out = o;
   }

   static String unitAddImport(CompilationUnit n) {
      StringWriter buf = new StringWriter();
      ImportInserter v = new ImportInserter(new PrintWriter(buf));
      n.accept(v);
      v.flushWriter();
      return buf.toString();
   }

   private void printImports(NodeListOptional n) {
      if ( Globals.nodePackage == "" ) return;

      boolean foundTreeImport = false;
      PrintWriter tempOut = out;

      for ( Enumeration e = n.elements(); e.hasMoreElements(); ) {
         ImportDeclaration dec = (ImportDeclaration)e.nextElement();
         StringWriter buf = new StringWriter();
         String s;

         out = new PrintWriter(buf);
         dec.accept(this);
         out.flush();
         s = buf.toString();
         tempOut.println("import " + s);

         if ( s.equals(Globals.nodePackage + ".*;") )
            foundTreeImport = true;
      }

      out = tempOut;

      if ( !foundTreeImport )
         out.println("import " + Globals.nodePackage + ".*;");
      
      // TODO: note: for syndiff!!!
      out.println("import lang.syntaxtree.*;");

      out.println("import java.util.Vector;");
   }

   //
   // f0 -> [ PackageDeclaration() ]
   // f1 -> ( ImportDeclaration() )*
   // f2 -> ( TypeDeclaration() )*
   // f3 -> < EOF >
   //
   public void visit(CompilationUnit n) {
      out.print(spc.spc);
      if ( n.f0.present() ) {
         visit(n.f0, "\n\n");
         out.print(spc.spc);
      }

      printImports(n.f1);
      out.print("\n\n" + spc.spc);

      if ( n.f2.present() )
         visit(n.f2, "\n" + spc.spc);

//
// builds specials into tree
//
if ( !Globals.keepSpecialTokens )
      out.println(
"\n\nclass JTBToolkit {\n" +
"   static NodeToken makeNodeToken(Token t) {\n" +
"      return new NodeToken(t.image.intern(), t.kind, t.beginLine, t.beginColumn, t.endLine, t.endColumn);\n" +
"   }\n" +
"}");

else
      out.println(
"\n\nclass JTBToolkit {\n" +
"   static NodeToken makeNodeToken(Token t) {\n" +
"      NodeToken node = new NodeToken(t.image.intern(), t.kind, t.beginLine, t.beginColumn, t.endLine, t.endColumn);\n\n" +
"      if ( t.specialToken == null )\n" +
"         return node;\n\n" +
"      Vector temp = new Vector();\n" +
"      Token orig = t;\n\n" +
"      while ( t.specialToken != null ) {\n" +
"         t = t.specialToken;\n" +
"         temp.addElement(new NodeToken(t.image.intern(), t.kind, t.beginLine, t.beginColumn, t.endLine, t.endColumn));\n" +
"      }\n\n" +
"      // Reverse the special token list\n" +
"      for ( int i = temp.size() - 1; i >= 0; --i )\n" +
"         node.addSpecial((NodeToken)temp.elementAt(i));\n\n" +
"      node.trimSpecials();\n" +
"      return node;\n" +
"   }\n" +
"}");

      out.println();
   }

   //
   // Does NOT print the "import".  That is left to printImports().
   //
   // f0 -> "import"
   // f1 -> Name()
   // f2 -> [ "." "*" ]
   // f3 -> ";"
   //
   public void visit(ImportDeclaration n) {
      n.f1.accept(this);
      n.f2.accept(this);
      out.print(n.f3);
   }
}
